var _ = require('underscore')
  , $ = require('jquery');
$.ajaxSettings.traditional = true;
function Descriptor(desc) {
  this._url = _.isFunction(desc.url)?desc.url:function(){return desc.url}
  this._paramsValidator = _.chain(desc.params).omit(function(v){
    return !_.isFunction(v.validate)
  }).mapObject(function(v){
    return v.validate
  }).value();
  this._defaultParams = _(desc.params).mapObject(function(v){
    return v.defaultValue
  })
  this._headers = desc.headers||{}
  this._optionParams = desc.optionParams;
  this._method = desc.method || 'get';
  this._dataType=desc.dataType || 'json';
  this._contentType = (desc.contentType||'application/x-www-form-urlencoded') + '; charset=UTF-8'
}

Descriptor.prototype.makeParams = function(paramsFns, defaultValue){
  return _.chain(paramsFns)
          .reduce(function(acc,v){
            var p = _.isFunction(v)?v.call(acc):v
            return _.extend({},acc,
              _.omit(p,function(value, key, object){
                return _.isUndefined(value)
              })
            )
          },defaultValue || this._defaultParams)
          .mapObject(function(v,k,a){
            return _.isFunction(v)?v.call(a):v
          },this).value()
}
Descriptor.prototype.validateParams = function(params){
  return _.chain(this._paramsValidator).map(function(validFn,k){
    var result = validFn(params[k],params)
    return result?{
      key:k,
      message:result
    }:null
  }).compact().value()
}
Descriptor.prototype.makeRequest = function(params){
  var data = _.omit.apply(_,[params].concat(this._optionParams))
  return {
    url:this._url(params),
    type:this._method,
    data:this._contentType.indexOf('application/json')>-1?JSON.stringify(data):data,
    dataType:this._dataType,
    cache:false,
    contentType:this._contentType,
    headers:this._headers
  }
}
module.exports = function ApiFactory(descriptor,options){
  options = options || {}
  var desc = new Descriptor(descriptor)
  return function createApiCaller(){
    var paramsFns = [].slice.call(arguments,0)
    return {
      append: function () {
        this.attachedArgs = _.toArray(arguments);
        return this;
      },
      post:function(preFilter){
        var _this = this
        var params = desc.makeParams(paramsFns);
        if (this.attachedArgs) {
          var attachedParams = desc.makeParams(this.attachedArgs, {});
          if (attachedParams) {
            _.extend(params, attachedParams);
          }
          this.attachedArgs = null;
        }
        var ajaxOption = desc.makeRequest(params)
        var validateResults = desc.validateParams(params)
        if(validateResults.length){
          return $.Deferred().reject(_.extend({},validateResults[0],{
            errors:_.chain(validateResults).map(function(i){
              return [i.key,i.message]
            }).object().value()
          }),params)
        }
        this.lastUrl = ajaxOption.url
        this.lastParams = params
        if(preFilter){
          ajaxOption = preFilter(ajaxOption)
        }
        var deferred = $.Deferred()
        deferred.notify(ajaxOption,params)
        this._xhr = $.ajax(ajaxOption);
        this._xhr.then(function(data){
          deferred.resolve(data,params)
        },function(err){
          deferred.reject(err,params)
        }).always(function(){
          _this._xhr = null
        })
        return deferred;
      },
      fetch:function(preFilter){
        var _this = this
        var params = desc.makeParams(paramsFns);
        if (this.attachedArgs) {
          var attachedParams = desc.makeParams(this.attachedArgs, {});
          if (attachedParams) {
            _.extend(params, attachedParams);
          }
          this.attachedArgs = null;
        }
        var ajaxOption = desc.makeRequest(params)
        if(this._xhr){
          this._xhr.abort()
          this._xhr = null
        }
        this.lastUrl = ajaxOption.url
        this.lastParams = params
        if(preFilter){
          ajaxOption = preFilter(ajaxOption)
        }
        var deferred = $.Deferred()
        deferred.notify(ajaxOption,params)
        this._xhr = $.ajax(ajaxOption);
        this._xhr.then(function(data){
          deferred.resolve(data,params)
        },function(err){
          deferred.reject(err,params)
        }).always(function(){
          _this._xhr = null
        })
        return deferred;
      }
    }
  }
}
